﻿// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;
using DataGridExtensions;
using ICSharpCode.Decompiler.Util;
using ICSharpCode.ILSpy.Controls;
using ICSharpCode.ILSpy.TextView;
using ICSharpCode.ILSpy.TreeNodes;
using ICSharpCode.ILSpy.ViewModels;

namespace ICSharpCode.ILSpy.Metadata
{
	static class Helpers
	{
		public static DataGrid PrepareDataGrid(TabPageModel tabPage, ILSpyTreeNode selectedNode)
		{
			if (!(tabPage.Content is DataGrid view && view.Name == "MetadataView")) {
				view = new MetaDataGrid() {
					Name = "MetadataView",
					GridLinesVisibility = DataGridGridLinesVisibility.None,
					CanUserAddRows = false,
					CanUserDeleteRows = false,
					CanUserReorderColumns = false,
					RowHeaderWidth = 0,
					EnableColumnVirtualization = true,
					EnableRowVirtualization = true,
					IsReadOnly = true,
					SelectionMode = DataGridSelectionMode.Single,
					SelectionUnit = DataGridSelectionUnit.CellOrRowHeader,
					SelectedTreeNode = selectedNode,
					CellStyle = new Style {
						Setters = {
							new Setter {
								Property = Control.BorderThicknessProperty,
								Value = new Thickness(0)
							}
						}
					}
				};
				ContextMenuProvider.Add(view);
				ScrollViewer.SetIsDeferredScrollingEnabled(view, true);
				DataGridFilter.SetIsAutoFilterEnabled(view, true);
				DataGridFilter.SetContentFilterFactory(view, new RegexContentFilterFactory());
			}
			view.RowDetailsTemplateSelector = null;
			view.RowDetailsVisibilityMode = DataGridRowDetailsVisibilityMode.Collapsed;
			((MetaDataGrid)view).SelectedTreeNode = selectedNode;
			if (!view.AutoGenerateColumns)
				view.Columns.Clear();
			view.AutoGenerateColumns = true;

			view.AutoGeneratingColumn += View_AutoGeneratingColumn;
			view.AutoGeneratedColumns += View_AutoGeneratedColumns;

			return view;
		}

		class MetaDataGrid : DataGrid, IHaveState
		{
			public ILSpyTreeNode SelectedTreeNode { get; set; }

			public ViewState GetState()
			{
				return new ViewState {
					DecompiledNodes = SelectedTreeNode == null ? null : new HashSet<ILSpyTreeNode>(new[] { SelectedTreeNode })
				};
			}
		}

		private static void View_AutoGeneratedColumns(object sender, EventArgs e)
		{
			((DataGrid)sender).AutoGeneratedColumns -= View_AutoGeneratedColumns;
			((DataGrid)sender).AutoGeneratingColumn -= View_AutoGeneratingColumn;
		}

		private static void View_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
		{
			var binding = new Binding(e.PropertyName) { Mode = BindingMode.OneWay };
			e.Column = new DataGridCustomTextColumn() {
				Header = e.PropertyName,
				Binding = binding,
				ToolTipBinding = new Binding(e.PropertyName + "Tooltip") { Mode = BindingMode.OneWay }
			};
			switch (e.PropertyName) {
				case "RID":
					e.Column.SetTemplate((ControlTemplate)MetadataTableViews.Instance["DefaultFilter"]);
					((DataGridCustomTextColumn)e.Column).ToolTipBinding = null;
					break;
				case "Token":
				case "Offset":
				case "RVA":
					binding.StringFormat = "X8";
					e.Column.SetTemplate((ControlTemplate)MetadataTableViews.Instance["HexFilter"]);
					((DataGridCustomTextColumn)e.Column).ToolTipBinding = null;
					break;
				default:
					e.Cancel = e.PropertyName.Contains("Tooltip");
					if (!e.Cancel) {
						e.Column.SetTemplate((ControlTemplate)MetadataTableViews.Instance["DefaultFilter"]);
					}
					break;
			}
			if (!e.Cancel) {
				ApplyAttributes((PropertyDescriptor)e.PropertyDescriptor, binding, e.Column);
			}
		}

		static void ApplyAttributes(PropertyDescriptor descriptor, Binding binding, DataGridColumn column)
		{
			if (descriptor.PropertyType.IsEnum) {
				binding.Converter = new UnderlyingEnumValueConverter();
				column.SetTemplate((ControlTemplate)MetadataTableViews.Instance[descriptor.PropertyType.Name + "Filter"]);
			}
			var stringFormat = descriptor.Attributes.OfType<StringFormatAttribute>().FirstOrDefault();
			if (stringFormat != null) {
				binding.StringFormat = stringFormat.Format;
				if (!descriptor.PropertyType.IsEnum && stringFormat.Format.StartsWith("X", StringComparison.OrdinalIgnoreCase)) {
					column.SetTemplate((ControlTemplate)MetadataTableViews.Instance["HexFilter"]);
				}
			}
		}

		[MethodImpl(MethodImplOptions.AggressiveInlining)]
		public static unsafe int GetValue(byte* ptr, int size)
		{
			int result = 0;
			for (int i = 0; i < size; i += 2) {
				result |= ptr[i] << 8 * i;
				result |= ptr[i + 1] << 8 * (i + 1);
			}
			return result;
		}

		static Helpers()
		{
			rowCounts = typeof(MetadataReader)
				.GetField("TableRowCounts", BindingFlags.NonPublic | BindingFlags.Instance);
			computeCodedTokenSize = typeof(MetadataReader)
				.GetMethod("ComputeCodedTokenSize", BindingFlags.Instance | BindingFlags.NonPublic);
			fromTypeDefOrRefTag = typeof(TypeDefinitionHandle).Assembly
				.GetType("System.Reflection.Metadata.Ecma335.TypeDefOrRefTag")
				.GetMethod("ConvertToHandle", BindingFlags.Static | BindingFlags.NonPublic);
			fromHasFieldMarshalTag = typeof(TypeDefinitionHandle).Assembly
				.GetType("System.Reflection.Metadata.Ecma335.HasFieldMarshalTag")
				.GetMethod("ConvertToHandle", BindingFlags.Static | BindingFlags.NonPublic);
			fromMemberForwardedTag = typeof(TypeDefinitionHandle).Assembly
				.GetType("System.Reflection.Metadata.Ecma335.MemberForwardedTag")
				.GetMethod("ConvertToHandle", BindingFlags.Static | BindingFlags.NonPublic);
		}

		readonly static FieldInfo rowCounts;
		readonly static MethodInfo computeCodedTokenSize;
		readonly static MethodInfo fromTypeDefOrRefTag;
		readonly static MethodInfo fromHasFieldMarshalTag;
		readonly static MethodInfo fromMemberForwardedTag;

		public static EntityHandle FromHasFieldMarshalTag(uint tag)
		{
			return (EntityHandle)fromHasFieldMarshalTag.Invoke(null, new object[] { tag });
		}

		public static EntityHandle FromMemberForwardedTag(uint tag)
		{
			return (EntityHandle)fromMemberForwardedTag.Invoke(null, new object[] { tag });
		}

		public static EntityHandle FromTypeDefOrRefTag(uint tag)
		{
			return (EntityHandle)fromTypeDefOrRefTag.Invoke(null, new object[] { tag });
		}

		public static int ComputeCodedTokenSize(this MetadataReader metadata, int largeRowSize, TableMask mask)
		{
			return (int)computeCodedTokenSize.Invoke(metadata, new object[] { largeRowSize, rowCounts.GetValue(metadata), (ulong)mask });
		}

		class UnderlyingEnumValueConverter : IValueConverter
		{
			public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
			{
				var t = value.GetType();
				if (t.IsEnum) {
					return (int)CSharpPrimitiveCast.Cast(TypeCode.Int32, value, false);
				}
				return value;
			}

			public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
			{
				throw new NotImplementedException();
			}
		}
	}

	class StringFormatAttribute : Attribute
	{
		public string Format { get; }

		public StringFormatAttribute(string format)
		{
			this.Format = format;
		}
	}

	[Flags]
	internal enum TableMask : ulong
	{
		Module = 0x1,
		TypeRef = 0x2,
		TypeDef = 0x4,
		FieldPtr = 0x8,
		Field = 0x10,
		MethodPtr = 0x20,
		MethodDef = 0x40,
		ParamPtr = 0x80,
		Param = 0x100,
		InterfaceImpl = 0x200,
		MemberRef = 0x400,
		Constant = 0x800,
		CustomAttribute = 0x1000,
		FieldMarshal = 0x2000,
		DeclSecurity = 0x4000,
		ClassLayout = 0x8000,
		FieldLayout = 0x10000,
		StandAloneSig = 0x20000,
		EventMap = 0x40000,
		EventPtr = 0x80000,
		Event = 0x100000,
		PropertyMap = 0x200000,
		PropertyPtr = 0x400000,
		Property = 0x800000,
		MethodSemantics = 0x1000000,
		MethodImpl = 0x2000000,
		ModuleRef = 0x4000000,
		TypeSpec = 0x8000000,
		ImplMap = 0x10000000,
		FieldRva = 0x20000000,
		EnCLog = 0x40000000,
		EnCMap = 0x80000000,
		Assembly = 0x100000000,
		AssemblyRef = 0x800000000,
		File = 0x4000000000,
		ExportedType = 0x8000000000,
		ManifestResource = 0x10000000000,
		NestedClass = 0x20000000000,
		GenericParam = 0x40000000000,
		MethodSpec = 0x80000000000,
		GenericParamConstraint = 0x100000000000,
		Document = 0x1000000000000,
		MethodDebugInformation = 0x2000000000000,
		LocalScope = 0x4000000000000,
		LocalVariable = 0x8000000000000,
		LocalConstant = 0x10000000000000,
		ImportScope = 0x20000000000000,
		StateMachineMethod = 0x40000000000000,
		CustomDebugInformation = 0x80000000000000,
		PtrTables = 0x4800A8,
		EncTables = 0xC0000000,
		TypeSystemTables = 0x1FC9FFFFFFFF,
		DebugTables = 0xFF000000000000,
		AllTables = 0xFF1FC9FFFFFFFF,
		ValidPortablePdbExternalTables = 0x1FC93FB7FF57
	}
}
